/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include <gtest/gtest.h>

#include <algorithm>
#include <array>
#include <cstdint>
#include <cstring>
#include <functional>
#include <string>
#include <vector>

#include <mc/common.h>
#include <mc/dict.h>
#include "../../../libraries/mctp/mctp.h"
#include "../../../libraries/ncsi/ncsi_protocol.h"
#include "../../../libraries/ncsi/ncsi_socket.h"
#include "../../../libraries/ncsi_over_mctp/ncsi_over_mctp_standard.h"

namespace mc {
class dict;
}

namespace dev {
mc::dict  get_ncsi_command_ctx(uint8_t package_id, uint8_t channel_id, uint8_t packet_type,
                               uint8_t expect_rsp_packet_type);
uint32_t  convert_speed_mbps(uint32_t speed_mbps);
bool      convert_full_duplex(uint8_t full_duplex);
uint8_t   convert_link_status(uint8_t link_status);
void set_mctp_request_handler(std::function<bool(const mc::dict&, const std::vector<uint8_t>&,
                                                 std::string&)> handler);
} // namespace dev

namespace {

constexpr uint16_t host_to_be16(uint16_t value) {
    return static_cast<uint16_t>(((value & 0x00FFU) << 8U) | ((value & 0xFF00U) >> 8U));
}

constexpr uint32_t host_to_be32(uint32_t value) {
    return ((value & 0x000000FFU) << 24U) | ((value & 0x0000FF00U) << 8U) |
           ((value & 0x00FF0000U) >> 8U) | ((value & 0xFF000000U) >> 24U);
}

constexpr uint64_t host_to_be64(uint64_t value) {
    return ((value & 0x00000000000000FFULL) << 56ULL) |
           ((value & 0x000000000000FF00ULL) << 40ULL) |
           ((value & 0x0000000000FF0000ULL) << 24ULL) |
           ((value & 0x00000000FF000000ULL) << 8ULL) |
           ((value & 0x000000FF00000000ULL) >> 8ULL) |
           ((value & 0x0000FF0000000000ULL) >> 24ULL) |
           ((value & 0x00FF000000000000ULL) >> 40ULL) |
           ((value & 0xFF00000000000000ULL) >> 56ULL);
}

class ncsi_over_mctp_standard_test : public ::testing::Test {
protected:
    ncsi_over_mctp_standard_test()
        : m_mctp(nullptr, 0, dev::MCTP_MESSAGE_TYPE_NCSI, ""), m_ncsi(m_mctp) {
    }

    void TearDown() override {
        dev::set_mctp_request_handler(nullptr);
    }

    template <typename T>
    static std::string to_binary_string(const T& value) {
        return std::string(reinterpret_cast<const char*>(&value), sizeof(T));
    }

    template <typename T>
    static std::string to_sized_string(const T& value, size_t length) {
        return std::string(reinterpret_cast<const char*>(&value), length);
    }

    dev::mctp                  m_mctp;
    dev::ncsi_over_mctp_standard m_ncsi;
};

TEST(ncsi_over_mctp_standard_utils, get_ncsi_command_ctx_populates_expected_fields) {
    auto ctx = dev::get_ncsi_command_ctx(/*package_id=*/0x0A, /*channel_id=*/0x03, GET_VERSION_ID,
                                         GET_VERSION_ID_RSP);
    ASSERT_FALSE(ctx.empty());
    EXPECT_EQ(ctx.at("PackageId").as<int64_t>(), 0x0A);
    EXPECT_EQ(ctx.at("ChannelId").as<int64_t>(), 0x03);
    EXPECT_EQ(ctx.at("PacketType").as<int64_t>(), GET_VERSION_ID);
    EXPECT_EQ(ctx.at("ExpectRspPacketType").as<int64_t>(), GET_VERSION_ID_RSP);
}

TEST(ncsi_over_mctp_standard_utils, convert_speed_mbps_handles_valid_and_invalid_inputs) {
    EXPECT_EQ(dev::convert_speed_mbps(1), 10U);
    EXPECT_EQ(dev::convert_speed_mbps(8), 10000U);
    EXPECT_EQ(dev::convert_speed_mbps(13), 100000U);
    EXPECT_EQ(dev::convert_speed_mbps(0), 0U);
    EXPECT_EQ(dev::convert_speed_mbps(30), 0U);
}

TEST(ncsi_over_mctp_standard_utils, convert_full_duplex_translates_flags) {
    EXPECT_FALSE(dev::convert_full_duplex(0));
    EXPECT_TRUE(dev::convert_full_duplex(1));
    EXPECT_FALSE(dev::convert_full_duplex(14));
    EXPECT_FALSE(dev::convert_full_duplex(40)); // 越界输入返回false
}

TEST(ncsi_over_mctp_standard_utils, convert_link_status_maps_known_values) {
    EXPECT_EQ(dev::convert_link_status(0x00), 0U);
    EXPECT_EQ(dev::convert_link_status(0x01), 1U);
    EXPECT_EQ(dev::convert_link_status(0xAA), 255U);
}

TEST_F(ncsi_over_mctp_standard_test, get_link_status_success) {
    dev::set_mctp_request_handler([](const mc::dict& ctx, const std::vector<uint8_t>& data,
                                     std::string& rsp) {
        EXPECT_EQ(ctx.at("PacketType").as<int64_t>(), GET_LINK_STATUS);
        EXPECT_TRUE(data.empty());
        link_status_rsp response{};
        response.rsp_code             = host_to_be16(COMMAND_COMPLETED);
        response.reason_code          = host_to_be16(NOERROR_OR_NOREASON);
        response.lnk_sts.link_flag    = 1;
        response.lnk_sts.speed_duplex = 3; // maps to 100Mbps
        rsp                            = to_sized_string(response, GET_LINK_STATUS_RSP_LEN);
        return true;
    });

    dev::ncsi_link_status_info_t info{};
    EXPECT_TRUE(m_ncsi.get_link_status(0, 0, &info));
    EXPECT_EQ(info.link_status, 1U);
    EXPECT_EQ(info.speed_mbps, dev::convert_speed_mbps(3));
    EXPECT_EQ(info.full_duplex, dev::convert_full_duplex(3));
}

TEST_F(ncsi_over_mctp_standard_test, get_link_status_request_failure_returns_false) {
    dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string&) {
        return false;
    });
    dev::ncsi_link_status_info_t info{};
    EXPECT_FALSE(m_ncsi.get_link_status(0, 0, &info));
}

TEST_F(ncsi_over_mctp_standard_test, get_link_status_rejects_invalid_size) {
    dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) {
        rsp.assign(4, '\0');
        return true;
    });
    dev::ncsi_link_status_info_t info{};
    EXPECT_FALSE(m_ncsi.get_link_status(0, 0, &info));
}

TEST_F(ncsi_over_mctp_standard_test, init_ncsi_channel_success) {
    dev::set_mctp_request_handler([](const mc::dict& ctx, const std::vector<uint8_t>& data,
                                     std::string& rsp) {
        EXPECT_EQ(ctx.at("PacketType").as<int64_t>(), CLEAR_INITIAL_STATE);
        EXPECT_TRUE(data.empty());
        rsp.clear();
        return true;
    });
    EXPECT_TRUE(m_ncsi.init_ncsi_channel(0, 0));
}

TEST_F(ncsi_over_mctp_standard_test, init_ncsi_channel_request_failure) {
    dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string&) {
        return false;
    });
    EXPECT_FALSE(m_ncsi.init_ncsi_channel(0, 0));
}

TEST_F(ncsi_over_mctp_standard_test, send_test_packet_success_with_payload) {
    dev::set_mctp_request_handler([](const mc::dict& ctx, const std::vector<uint8_t>& data,
                                     std::string& rsp) {
        EXPECT_EQ(ctx.at("PacketType").as<int64_t>(), 0x5AU);
        EXPECT_EQ(data, std::vector<uint8_t>({'h', 'i'}));
        rsp = "ok";
        return true;
    });
    EXPECT_TRUE(m_ncsi.send_test_packet(0, 0, 0x5A, "hi"));
}

TEST_F(ncsi_over_mctp_standard_test, send_test_packet_request_failure) {
    dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string&) {
        return false;
    });
    EXPECT_FALSE(m_ncsi.send_test_packet(0, 0, 0x5A, "hi"));
}

TEST_F(ncsi_over_mctp_standard_test, send_test_packet_empty_response_is_failure) {
    dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>& data,
                                     std::string& rsp) {
        rsp.clear();
        return true;
    });
    EXPECT_FALSE(m_ncsi.send_test_packet(0, 0, 0x5A, "hi"));
}

TEST_F(ncsi_over_mctp_standard_test, get_version_id_success) {
    dev::set_mctp_request_handler([](const mc::dict& ctx, const std::vector<uint8_t>&,
                                     std::string& rsp) {
        EXPECT_EQ(ctx.at("PacketType").as<int64_t>(), GET_VERSION_ID);
        get_version_id_rsp response{};
        response.rsp_code    = host_to_be16(COMMAND_COMPLETED);
        response.reason_code = host_to_be16(NOERROR_OR_NOREASON);
        response.firmware_ver[0] = 10;
        response.firmware_ver[1] = 20;
        response.firmware_ver[2] = 30;
        response.firmware_ver[3] = 40;
        response.pci_vid          = host_to_be16(0x1234);
        response.pci_did          = host_to_be16(0x5678);
        response.pci_ssid         = host_to_be16(0x9ABC);
        response.pci_svid         = host_to_be16(0x0001);
        rsp = to_sized_string(response, GET_VERSION_ID_RSP_LEN);
        return true;
    });

    dev::ncsi_version_id_info_t info{};
    EXPECT_TRUE(m_ncsi.get_version_id(0, 0, &info));
    char firmware_version_str[16] = {0};
    snprintf(firmware_version_str, sizeof(firmware_version_str), "%d.%d.%d.%d",
            info.firmware_ver[0], info.firmware_ver[1], info.firmware_ver[2], info.firmware_ver[3]);
    EXPECT_EQ(std::string(firmware_version_str), "10.20.30.40");
    EXPECT_EQ(info.vid, "0x1234");
    EXPECT_EQ(info.did, "0x5678");
    EXPECT_EQ(info.svid, "0x0001");
    EXPECT_EQ(info.sdid, "0x9abc");
}

TEST_F(ncsi_over_mctp_standard_test, get_version_id_request_failure) {
    dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string&) {
        return false;
    });
    dev::ncsi_version_id_info_t info{};
    EXPECT_FALSE(m_ncsi.get_version_id(0, 0, &info));
}

TEST_F(ncsi_over_mctp_standard_test, get_version_id_invalid_size) {
    dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) {
        rsp.assign(8, '\0');
        return true;
    });
    dev::ncsi_version_id_info_t info{};
    EXPECT_FALSE(m_ncsi.get_version_id(0, 0, &info));
}

TEST_F(ncsi_over_mctp_standard_test, get_port_metrics_success) {
    dev::set_mctp_request_handler([](const mc::dict& ctx, const std::vector<uint8_t>&,
                                     std::string& rsp) {
        EXPECT_EQ(ctx.at("PacketType").as<int64_t>(), GET_CONTROLLER_PACKET_STATISTICS);
        controller_packet_statistics_rsp response{};
        response.rsp_code                         = host_to_be16(COMMAND_COMPLETED);
        response.reason_code                      = host_to_be16(NOERROR_OR_NOREASON);
        response.total_bytes_received             = host_to_be64(10);
        response.total_bytes_transmitted          = host_to_be64(20);
        response.total_unicast_packets_received   = host_to_be64(30);
        response.total_unicast_packets_transmitted= host_to_be64(40);
        response.total_multicast_packets_received = host_to_be64(50);
        response.total_multicast_packets_transmitted = host_to_be64(60);
        response.total_broadcast_packets_received = host_to_be64(70);
        response.total_broadcast_packets_transmitted = host_to_be64(80);
        response.fcs_received_errors              = host_to_be32(90);
        response.runt_packets_received            = host_to_be32(100);
        response.jabber_packets_received          = host_to_be32(110);
        response.single_collision_transmit_frames = host_to_be32(120);
        response.multiple_collision_transmit_frames = host_to_be32(130);
        response.late_collision_frames            = host_to_be32(140);
        response.excessive_collision_frames       = host_to_be32(150);
        response.alignment_errors                 = host_to_be32(160);
        rsp = to_sized_string(response, GET_CONTROLLER_PACKET_STATISTICS_RSP_LEN);
        return true;
    });

    dev::ncsi_port_metrics_info_t info{};
    EXPECT_TRUE(m_ncsi.get_port_metrics(0, 0, &info));
    EXPECT_EQ(info.rx_frames, 10U);
    EXPECT_EQ(info.tx_frames, 20U);
    EXPECT_EQ(info.rx_fcs_errors, 90U);
}

TEST_F(ncsi_over_mctp_standard_test, get_port_metrics_request_failure) {
    dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string&) {
        return false;
    });
    dev::ncsi_port_metrics_info_t info{};
    EXPECT_FALSE(m_ncsi.get_port_metrics(0, 0, &info));
}

TEST_F(ncsi_over_mctp_standard_test, get_port_metrics_invalid_size) {
    dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) {
        rsp.assign(8, '\0');
        return true;
    });
    dev::ncsi_port_metrics_info_t info{};
    EXPECT_FALSE(m_ncsi.get_port_metrics(0, 0, &info));
}

TEST_F(ncsi_over_mctp_standard_test, get_capabilities_success) {
    dev::set_mctp_request_handler([](const mc::dict& ctx, const std::vector<uint8_t>&,
                                     std::string& rsp) {
        EXPECT_EQ(ctx.at("PacketType").as<int64_t>(), GET_CAPABILITIES);
        get_capability_rsp response{};
        response.rsp_code             = host_to_be16(COMMAND_COMPLETED);
        response.reason_code          = host_to_be16(NOERROR_OR_NOREASON);
        response.capality_flag        = host_to_be32(0x11223344);
        response.broadcast_filter_cap = host_to_be32(1);
        response.multicast_filter_cap = host_to_be32(2);
        response.buffer_cap           = host_to_be32(3);
        response.aen_support          = host_to_be32(4);
        response.vlan_filter_cnt      = 5;
        response.mix_filter_cnt       = 6;
        response.mul_filter_cnt       = 7;
        response.unicast_filter_cnt   = 8;
        response.vlan_mode            = 9;
        response.channel_cnt          = 10;
        rsp                           = to_sized_string(response, GET_CAPABILITIES_RSP_LEN);
        return true;
    });

    dev::ncsi_capabilities_info_t info{};
    EXPECT_TRUE(m_ncsi.get_capabilities(0, 0, &info));
    EXPECT_EQ(info.capality_flag, 0x11223344U);
    EXPECT_EQ(info.vlan_filter_cnt, 5U);
    EXPECT_EQ(info.channel_cnt, 10U);
}

TEST_F(ncsi_over_mctp_standard_test, get_capabilities_request_failure) {
    dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string&) {
        return false;
    });
    dev::ncsi_capabilities_info_t info{};
    EXPECT_FALSE(m_ncsi.get_capabilities(0, 0, &info));
}

TEST_F(ncsi_over_mctp_standard_test, get_capabilities_invalid_size_returns_false) {
    dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) {
        rsp.assign(8, '\0');
        return true;
    });
    dev::ncsi_capabilities_info_t info{};
    EXPECT_FALSE(m_ncsi.get_capabilities(0, 0, &info));
}

TEST_F(ncsi_over_mctp_standard_test, get_parameters_success) {
    dev::set_mctp_request_handler([](const mc::dict& ctx, const std::vector<uint8_t>&,
                                     std::string& rsp) {
        EXPECT_EQ(ctx.at("PacketType").as<int64_t>(), GET_PARAMETERS);
        get_parameters_rsp response{};
        response.rsp_code     = host_to_be16(COMMAND_COMPLETED);
        response.reason_code  = host_to_be16(NOERROR_OR_NOREASON);
        response.mac_cnt      = 1;
        response.mac_enable   = 2;
        response.vlan_cnt     = 3;
        response.vlan_enable  = host_to_be16(0x1234);
        response.link_mode    = host_to_be32(0x11111111);
        response.bc_mode      = host_to_be32(0x22222222);
        response.valid_modes  = host_to_be32(0x33333333);
        response.vlan_mode    = 4;
        response.fc_mode      = 5;
        response.aen_mode     = host_to_be32(0x44444444);
        std::array<uint8_t, 6> mac = {1, 2, 3, 4, 5, 6};
        std::copy(mac.begin(), mac.end(), response.mac);
        response.vlan = host_to_be16(0x4321);
        rsp = to_sized_string(response, GET_PARAMETERS_RSP_LEN);
        return true;
    });

    dev::ncsi_parameters_info_t info{};
    EXPECT_TRUE(m_ncsi.get_parameters(0, 0, &info));
    EXPECT_EQ(info.mac_cnt, 1U);
    EXPECT_EQ(info.vlan_enable, 0x1234U);
    EXPECT_EQ(info.mac[0], 1U);
    EXPECT_EQ(info.vlan, 0x4321U);
}

TEST_F(ncsi_over_mctp_standard_test, get_parameters_request_failure) {
    dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string&) {
        return false;
    });
    dev::ncsi_parameters_info_t info{};
    EXPECT_FALSE(m_ncsi.get_parameters(0, 0, &info));
}

TEST_F(ncsi_over_mctp_standard_test, get_parameters_invalid_size_returns_false) {
    dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string& rsp) {
        rsp.assign(8, '\0');
        return true;
    });
    dev::ncsi_parameters_info_t info{};
    EXPECT_FALSE(m_ncsi.get_parameters(0, 0, &info));
}

} // namespace

